use cxx::UniquePtr;
use opencascade_sys::ffi;
use opencascade_sys::ffi::{BRep_Builder, BRep_Builder_ctor, BRep_Builder_upcast_to_topods_builder, TopoDS_Shell_as_shape, TopoDS_Shell_ctor};
use crate::Error;

use crate::primitives::{Wire, Face};

pub struct Shell {
    pub(crate) inner: UniquePtr<ffi::TopoDS_Shell>,
}

impl AsRef<Shell> for Shell {
    fn as_ref(&self) -> &Shell {
        self
    }
}

impl Shell {
    pub(crate) fn from_shell(shell: &ffi::TopoDS_Shell) -> Self {
        let inner = ffi::TopoDS_Shell_to_owned(shell);

        Self { inner }
    }

    pub fn from_faces(faces: Vec<Face>) -> Result<Self, Error> {
        // let mut shell = TopoDS_Shell::new();
        let mut shell = TopoDS_Shell_ctor();
        let builder = BRep_Builder_ctor();
        let builder = BRep_Builder_upcast_to_topods_builder(&builder);

        builder.MakeShell(shell.pin_mut());
        let mut shape = TopoDS_Shell_as_shape(shell);

        for face in faces {
            builder.Add(shape.pin_mut(), ffi::cast_face_to_shape(&face.inner));
        }

        Ok(Self{
            inner: ffi::TopoDS_Shell_to_owned(ffi::TopoDS_cast_to_shell(&shape)),
        })
    }

    pub fn loft<T: AsRef<Wire>>(wires: impl IntoIterator<Item = T>) -> Self {
        let is_solid = false;
        let mut make_loft = ffi::BRepOffsetAPI_ThruSections_ctor(is_solid, true);

        for wire in wires.into_iter() {
            make_loft.pin_mut().AddWire(&wire.as_ref().inner);
        }

        // Set CheckCompatibility to `true` to avoid twisted results.
        make_loft.pin_mut().CheckCompatibility(true);

        let shape = make_loft.pin_mut().Shape();
        let shell = ffi::TopoDS_cast_to_shell(shape);

        Self::from_shell(shell)
    }
}
